ECMAScript2016规范理解(10)- 函数,方法、类

函数、方法、类在规范中的定义理解

函数

目前在规范中规定一共有6种函数:

创建函数

  • 创建函数一共有三种方式:函数声明、函数表达式、Function构造函数
  • 函数声明函数表达式在语法上的区别就是,函数表达式中的函数名是可选的。在实际使用中需要注意使用函数表达式创建的函数不会有”变量提升“,即必须在使用前声明
  • 函数声明提升的几种情况:函数表达式,块级函数

ECMAScript中规定的创建函数的算法

  • 普通函数
    • FunctionCreate(Normal, FormalParameters, FunctionBody, funcEnv).
    • MakeConstructor(closure).
  • 箭头函数
    • FunctionCreate(Arrow, FormalParameters, FunctionBody, funcEnv).
  • 都是通过 FunctionCreate 过程实现函数创建,但是箭头函数不会执行MakeConstructor,所以箭头函数不能用作构造函数,并且prototype属性是在MakeConstructor里定义的,所以箭头函数也没有prototype属性
FunctionCreate方法详细说明
  • FunctionAllocate(prototype, functionkind)
    • 创建一个ecma函数对象F,同时F.[[FunctionKind]]为functionKind。F.[[Prototype]] to functionPrototype.
    • arrow function 不会初始化F.[[Construct]] 属性
    • 返回 F
  • FunctionInitialize
    • 箭头函数的 F.[[ThisMode]] 设置为 lexical.普通函数的是 F.[[ThisMode]] to global

执行函数

  • 执行函数即函数调用,从预发上来说是执行一个CallExpression, 其具体执行过程在12.3.4 Function Calls, 简化下就是:
    • 先确定this值,如a.b()这种形式,this为a
    • 调用F.Call
  • 执行函数最终都是执行F.[Call]方法。
  • F.[[Call]]执行过程中会执行 OrdinaryCallBindThis ,它的作用就是给当前执行环境绑定this的值,这样在执行函数体的时候,知道到this值,就知道this的值是什么了。在OrdinaryCallBindThis执行的时候,如果[[ThisMode]]为 lexical。则直接return,其他情况会envRec.BindThisValue(thisValue).即给当前执行环境中绑定传递过来的this的值。所以箭头函数内的词法环境记录项里是没有this的。所以只能往上找,即当时定义的词法环境记录项。而且bind、call、apply最后都要调用F.[[Call]],所以对箭头函数都无法生效

方法(Method)

方法的定义

  • 规范定义中看,方法一种有6种:普通方法、Generator方法,Async方法,AsyncGenerator方法,set方法,get方法
  • DefineMethod执行过程(必选参数object,prototype为可选参数)
    • FunctionCreate(kind, UniqueFormalParameters, FunctionBody, scope, prototype)。创建了一个函数,并且F.[[Prototype]] 为prototype参数
    • MakeMethod(closure, object),就是把closure的[[HomeObject]]内置属性设置为object
  • FunctionCreate执行时,kind是Method,同样的没有执行MakeConstructor,所以也不能当做构造函数使用

方法的执行

  • 方法执行代码例如a.b()是一个左值表达式,最终走的还是函数调用的逻辑

类(Class)

  • 14.6 Class Definitions
    • 给class name在当前词法环境记录项中绑定一个不可变的名字
    • 如果有继承,找到继承的class p,则以p.prototype为原型创建对象proto,以 p 为constructorParent
    • 如果不是继承,则以Object.prototype为原型创建对象proto,以Function.prototype为constructorParent
    • 对constructor以proto和constructorParent为参数执行 DefineMethod 得到函数F,并且F.[[Prototype]] constructorParent
    • 对F执行MakeConstructor(F, false, proto)。得到 F.prototype为proto
    • 给proto添加属性constructor为F
    • 处理classbody代码,如果是static方法,全部以F为参数执行 DefineMethod,不是的话已proto为参数执行 DefineMethod
    • 返回F
  • 如果是继承的情况,为何要让p成为F的[[prototype]]属性
  • 看示例
    1
    2
    3
    4
    5
    6
    7
    8
    class a {}
    class b extends a{} // 这句会导致 b.__proto__ === a, b.prototype.__proto__ === a.prototype
    const c = new a()
    c instanceof b // true c.__proto__ === b.prototype
    c instanceof a // true c.__proto__.__proto__ === a.prototype
    b instanceof Function // true b.__proto__ === a, a.__proto__ === Function.prototype
    // 因为 c.__proto__ === b.prototype === Object.create(a.prototype)
    // 所以 c.__proto__.__proto__ === b.prototype.__proto__ === a.prototype